import { PageLink } from '../page/page-link';
import { BehaviorSubject, Observable, of, ReplaySubject, Subscription, catchError, map, share, take, tap } from 'rxjs';
import { emptyPageData, PageData } from '../page/page-data';
import { BaseData } from '../base-data';
import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections';
import { ItemBooleanFunction } from './table-config.models';

export type TableFetchFunction<T extends BaseData, P extends PageLink> = (pageLink: P) => Observable<PageData<T>>;

export class TableDataSource<T extends BaseData, P extends PageLink = PageLink> implements DataSource<T> {
  private itemsSubject = new BehaviorSubject<T[]>([]);
  private pageDataSubject = new BehaviorSubject<PageData<T>>(emptyPageData<T>());
  private currentLoadSubscription: Subscription | null = null;

  public pageData$ = this.pageDataSubject.asObservable();

  public selection = new SelectionModel<T>(true, []);

  public currentItem: T | null = null;

  public dataLoading = true;

  constructor(
    private fetchFunction: TableFetchFunction<T, P>,
    protected selectionEnabledFunction: ItemBooleanFunction<T>,
    protected dataLoadedFunction: (col?: number, row?: number) => void
  ) {}

  connect(collectionViewer: CollectionViewer): Observable<T[] | ReadonlyArray<T>> {
    return this.itemsSubject.asObservable();
  }

  disconnect(collectionViewer: CollectionViewer): void {
    this.itemsSubject.complete();
    this.pageDataSubject.complete();
  }

  reset(): void {
    const pageData = emptyPageData<T>();
    this.onItems(pageData.data);
    this.pageDataSubject.next(pageData);
    this.dataLoadedFunction();
  }

  loadItems(pageLink: P): Observable<PageData<T>> {
    if (this.currentLoadSubscription) {
      this.currentLoadSubscription.unsubscribe();
    }
    this.dataLoading = true;
    const result = new ReplaySubject<PageData<T>>();
    this.currentLoadSubscription = this.fetchFunction(pageLink)
      .pipe(
        tap(() => {
          this.selection.clear();
        }),
        catchError(() => of(emptyPageData<T>()))
      )
      .subscribe((pageData) => {
        this.onItems(pageData.data);
        this.pageDataSubject.next(pageData);
        result.next(pageData);
        this.dataLoadedFunction();
        this.dataLoading = false;
      });
    return result;
  }

  protected onItems(items: T[]): void {
    this.itemsSubject.next(items);
  }

  isAllSelected(): Observable<boolean> {
    const numSelected = this.selection.selected.length;
    return this.itemsSubject.pipe(
      map((items) => numSelected === this.selectableItemsCount(items)),
      share()
    );
  }

  isEmpty(): Observable<boolean> {
    return this.itemsSubject.pipe(
      map((items) => !items.length),
      share()
    );
  }

  total(): Observable<number> {
    return this.pageDataSubject.pipe(
      map((pageData) => pageData.totalElements),
      share()
    );
  }

  toggleCurrentItem(item: T): boolean {
    if (this.currentItem !== item) {
      this.currentItem = item;
      return true;
    } else {
      return false;
    }
  }

  isCurrentItem(item: T): boolean {
    return (
      this.currentItem !== null &&
      item !== null &&
      this.currentItem.id !== undefined &&
      item.id !== undefined &&
      this.currentItem.id === item.id
    );
  }

  masterToggle(): void {
    this.itemsSubject
      .pipe(
        tap((items) => {
          const numSelected = this.selection.selected.length;
          if (numSelected === this.selectableItemsCount(items)) {
            this.selection.clear();
          } else {
            items.forEach((row) => {
              if (this.selectionEnabledFunction(row)) {
                this.selection.select(row);
              }
            });
          }
        }),
        take(1)
      )
      .subscribe();
  }

  private selectableItemsCount(items: Array<T>): number {
    return items.filter((item) => this.selectionEnabledFunction(item)).length;
  }
}

